﻿// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

using ICSharpCode.NRefactory.Cpp.Ast;
using ICSharpCode.NRefactory.Cpp.Visitors;

namespace ICSharpCode.NRefactory.Cpp.Parser
{
    internal partial class CppParser : IDisposable
    {
        CppLexer lexer;
        Stack<AstNode> stack;
        CompilationUnit compilationUnit;
        int errDist = MinErrDist;

        const int MinErrDist = 2;
        const string ErrMsgFormat = "-- line {0} col {1}: {2}";  // 0=line, 1=column, 2=text

        public CppParser(CppLexer lexer)
        {
            this.errors = lexer.Errors;
            errors.SynErr = new ErrorCodeProc(SynErr);
            this.lexer = (CppLexer)lexer;
            this.stack = new Stack<AstNode>();
        }

        #region Infrastructure
        void NodeStart(AstNode node)
        {
            stack.Push(node);
        }

        void NodeEnd(AstNode currentNode, Role role)
        {
            AstNode node = stack.Pop();
            Debug.Assert(currentNode == node);
            stack.Peek().AddChildUntyped(node, role);
        }

        void AddTerminal(Role<CppTokenNode> role)
        {
            stack.Peek().AddChild(new CppTokenNode(t.Location, t.EndLocation), role);
        }

        void AddChild<T>(T childNode, Role<T> role) where T : AstNode
        {
            if (childNode != null)
            {
                stack.Peek().AddChild(childNode, role);
            }
        }

        StringBuilder qualidentBuilder = new StringBuilder();

        Token t
        {
            [System.Diagnostics.DebuggerStepThrough]
            get
            {
                return lexer.Token;
            }
        }

        Token la
        {
            [System.Diagnostics.DebuggerStepThrough]
            get
            {
                return lexer.LookAhead;
            }
        }

        Token Peek(int n)
        {
            lexer.StartPeek();
            Token x = la;
            while (n > 0)
            {
                x = lexer.Peek();
                n--;
            }
            return x;
        }

        public void Error(string s)
        {
            if (errDist >= MinErrDist)
            {
                this.Errors.Error(la.line, la.col, s);
            }
            errDist = 0;
        }
        #endregion

        #region Parse
        public void Parse()
        {
            ParseRoot();
        }

        public AstType ParseAstType()
        {
            // TODO
            return null;
        }

        //		public Expression ParseExpression()
        //		{
        //			lexer.SetInitialContext(SnippetType.Expression);
        //			lexer.NextToken();
        //			Location startLocation = la.Location;
        //			Expression expr;
        //			Expr(out expr);
        //			while (la.kind == Tokens.EOL) lexer.NextToken();
        //			if (expr != null) {
        //				expr.StartLocation = startLocation;
        //				expr.EndLocation = t.EndLocation;
        //				expr.AcceptVisitor(new SetParentVisitor(), null);
        //			}
        //			Expect(Tokens.EOF);
        //			return expr;
        //		}

        //		public BlockStatement ParseBlock()
        //		{
        //			lexer.NextToken();
        //			compilationUnit = new CompilationUnit();
        //
        //			Location startLocation = la.Location;
        //			Statement st;
        //			Block(out st);
        //			if (st != null) {
        //				st.StartLocation = startLocation;
        //				if (t != null)
        //					st.EndLocation = t.EndLocation;
        //				else
        //					st.EndLocation = la.Location;
        //				st.AcceptVisitor(new SetParentVisitor(), null);
        //			}
        //			Expect(Tokens.EOF);
        //			return st as BlockStatement;
        //		}

        //		public List<AstNode> ParseTypeMembers()
        //		{
        //			lexer.NextToken();
        //			TypeDeclaration newType = new TypeDeclaration(Modifiers.None, null);
        //			BlockStart(newType);
        //			ClassBody(newType);
        //			BlockEnd();
        //			Expect(Tokens.EOF);
        //			newType.AcceptVisitor(new SetParentVisitor(), null);
        //			return newType.Children;
        //		}
        #endregion

        #region Conflict Resolvers
        bool IsAliasImportsClause()
        {
            return IsIdentifierToken(la) && Peek(1).Kind == Tokens.Assign;
        }

        static bool IsIdentifierToken(Token tk)
        {
            return Tokens.IdentifierTokens[tk.kind] || tk.kind == Tokens.Identifier;
        }
        #endregion

        /* True, if "." is followed by an ident */
        bool DotAndIdentOrKw()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.Dot && (peek == Tokens.Identifier || peek >= Tokens.AddHandler);
        }

        bool IsIdentifiedExpressionRange()
        {
            // t = Select
            // la = Identifier
            // Peek(1) = As or Assign
            Token token = Peek(1);
            return IsIdentifierToken(la) && (token.kind == Tokens.As || token.kind == Tokens.Assign);
        }

        bool IsQueryExpression()
        {
            return (la.kind == Tokens.From || la.kind == Tokens.Aggregate) && IsIdentifierToken(Peek(1));
        }

        bool IsEndStmtAhead()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.End && (peek == Tokens.EOL || peek == Tokens.Colon);
        }

        bool IsNotClosingParenthesis()
        {
            return la.kind != Tokens.CloseParenthesis;
        }

        /*
            True, if ident is followed by "=" or by ":" and "="
         */
        bool IsNamedAssign()
        {
            return Peek(1).kind == Tokens.ColonAssign;
        }

        bool IsObjectCreation()
        {
            return la.kind == Tokens.As && Peek(1).kind == Tokens.New;
        }

        bool IsNewExpression()
        {
            return la.kind == Tokens.New;
        }

        /*
            True, if "<" is followed by the ident "assembly" or "module"
         */
        bool IsGlobalAttrTarget()
        {
            Token pt = Peek(1);
            return la.kind == Tokens.LessThan && (string.Equals(pt.val, "assembly", StringComparison.InvariantCultureIgnoreCase) || string.Equals(pt.val, "module", StringComparison.InvariantCultureIgnoreCase));
        }

        /*
            True if the next token is a "(" and is followed by "," or ")"
         */
        bool IsDims()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.OpenParenthesis
                && (peek == Tokens.Comma || peek == Tokens.CloseParenthesis);
        }

        /*
            True if the next token is an identifier
         */
        bool IsLoopVariableDeclaration()
        {
            if (!IsIdentifierToken(la))
                return false;
            lexer.StartPeek();
            Token x = lexer.Peek();
            if (x.kind == Tokens.OpenParenthesis)
            {
                do
                {
                    x = lexer.Peek();
                } while (x.kind == Tokens.Comma);
                if (x.kind != Tokens.CloseParenthesis)
                    return false;
                x = lexer.Peek();
            }
            return x.kind == Tokens.As || x.kind == Tokens.Assign;
        }

        bool IsSize()
        {
            return la.kind == Tokens.OpenParenthesis;
        }

        /*
            True, if the comma is not a trailing one,
            like the last one in: a, b, c,
         */
        bool NotFinalComma()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.Comma &&
                peek != Tokens.CloseCurlyBrace;
        }

        /*
            True, if the next token is "Else" and this one
            if followed by "If"
         */
        bool IsElseIf()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.Else && peek == Tokens.If;
        }

        /*
    True if the next token is goto and this one is
    followed by minus ("-") (this is allowd in in
    error clauses)
         */
        bool IsNegativeLabelName()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.GoTo && peek == Tokens.Minus;
        }

        /*
    True if the next statement is a "Resume next" statement
         */
        bool IsResumeNext()
        {
            int peek = Peek(1).kind;
            return la.kind == Tokens.Resume && peek == Tokens.Next;
        }

        /// <summary>
        /// Returns True, if ident/literal integer is followed by ":"
        /// </summary>
        bool IsLabel()
        {
            return (la.kind == Tokens.Identifier || la.kind == Tokens.LiteralInteger)
                && Peek(1).kind == Tokens.Colon;
        }

        /// <summary>
        /// Returns true if a property declaration is an automatic property.
        /// </summary>
        bool IsAutomaticProperty()
        {
            lexer.StartPeek();
            Token tn = la;
            int braceCount = 0;

            // look for attributes
            while (tn.kind == Tokens.LessThan)
            {
                while (braceCount > 0 || tn.kind != Tokens.GreaterThan)
                {
                    tn = lexer.Peek();
                    if (tn.kind == Tokens.OpenParenthesis)
                        braceCount++;
                    if (tn.kind == Tokens.CloseParenthesis)
                        braceCount--;
                }
                Debug.Assert(tn.kind == Tokens.GreaterThan);
                tn = lexer.Peek();
            }

            // look for modifiers
            var allowedTokens = new[] {
				Tokens.Public, Tokens.Protected,
				Tokens.Friend, Tokens.Private
			};

            while (allowedTokens.Contains(tn.kind))
                tn = lexer.Peek();

            if (tn.Kind != Tokens.Get && tn.Kind != Tokens.Set)
                return true;

            return false;
        }

        bool IsNotStatementSeparator()
        {
            return la.kind == Tokens.Colon && Peek(1).kind == Tokens.EOL;
        }

        static bool IsMustOverride(AttributedNode node)
        {
            return node.Modifiers.HasFlag(Modifiers.MustOverride);
        }

        /* Writes the type name represented through the expression into the string builder. */
        /* Returns true when the expression was converted successfully, returns false when */
        /* There was an unknown expression (e.g. TypeReferenceExpression) in it */
        //		bool WriteFullTypeName(StringBuilder b, Expression expr)
        //		{
        //			MemberReferenceExpression fre = expr as MemberReferenceExpression;
        //			if (fre != null) {
        //				bool result = WriteFullTypeName(b, fre.TargetObject);
        //				if (b.Length > 0) b.Append('.');
        //				b.Append(fre.MemberName);
        //				return result;
        //			} else if (expr is SimpleNameExpression) {
        //				b.Append(((SimpleNameExpression)expr).Identifier);
        //				return true;
        //			} else {
        //				return false;
        //			}
        //		}

        void EnsureIsZero(Expression expr)
        {
            if (!(expr is PrimitiveExpression) || (expr as PrimitiveExpression).StringValue != "0")
                Error("lower bound of array must be zero");
        }

        public bool ParseMethodBodies { get; set; }

        public CppLexer Lexer
        {
            get
            {
                return lexer;
            }
        }

        public Errors Errors
        {
            get
            {
                return errors;
            }
        }

        public CompilationUnit CompilationUnit
        {
            get
            {
                return compilationUnit;
            }
        }

        void SynErr(int n)
        {
            if (errDist >= MinErrDist)
            {
                errors.SynErr(lexer.LookAhead.line, lexer.LookAhead.col, n);
            }
            errDist = 0;
        }

        void SemErr(string msg)
        {
            if (errDist >= MinErrDist)
            {
                errors.Error(lexer.Token.line, lexer.Token.col, msg);
            }
            errDist = 0;
        }

        void Expect(int n)
        {
            if (lexer.LookAhead.kind == n)
            {
                lexer.NextToken();
            }
            else
            {
                SynErr(n);
            }
        }

        #region System.IDisposable interface implementation
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        public void Dispose()
        {
            errors = null;
            if (lexer != null)
            {
                lexer.Dispose();
            }
            lexer = null;
        }
        #endregion
    }
}
